package com.runjian.rundo.iot.agent.iec104.codec;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.runjian.rundo.iot.agent.common.entity.AgentDeviceDTO;
import com.runjian.rundo.iot.agent.common.enums.DeviceMessageType;
import com.runjian.rundo.iot.agent.iec104.codec.handler.IIec104FrameStrategy;
import com.runjian.rundo.iot.agent.iec104.consts.Constans;
import com.runjian.rundo.iot.agent.iec104.consts.DeviceType;
import com.runjian.rundo.iot.agent.iec104.enums.UFrameControlType;
import com.runjian.rundo.iot.agent.iec104.parser.frame.control.base.BaseControlFrame;
import org.springframework.stereotype.Component;

import com.runjian.rundo.iot.agent.business.control.cache.DeviceControlCache;
import com.runjian.rundo.iot.agent.business.control.enums.ControlResult;
import com.runjian.rundo.iot.agent.business.control.event.DeviceControlResponseEvent;
import com.runjian.rundo.iot.agent.business.publisher.DeviceEventPublisher;
import com.runjian.rundo.iot.agent.common.codec.DeviceMessageCodec;
import com.runjian.rundo.iot.agent.common.context.DeviceMessageDecodeContext;
import com.runjian.rundo.iot.agent.common.context.DeviceMessageEncodeContext;
import com.runjian.rundo.iot.agent.common.entity.CommonDeviceCommand;
import com.runjian.rundo.iot.agent.common.enums.SessionIdleType;
import com.runjian.rundo.iot.agent.common.message.DecodedMessage;
import com.runjian.rundo.iot.agent.common.message.DefaultDecodedMessage;
import com.runjian.rundo.iot.agent.common.message.DefaultDeviceMessage;
import com.runjian.rundo.iot.agent.common.message.DeviceMessage;
import com.runjian.rundo.iot.agent.common.session.Session;
import com.runjian.rundo.iot.agent.iec104.action.ActionHandleManager;
import com.runjian.rundo.iot.agent.iec104.consts.Action;
import com.runjian.rundo.iot.agent.iec104.consts.Ti;
import com.runjian.rundo.iot.agent.iec104.manager.Iec104DeviceManager;
import com.runjian.rundo.iot.agent.iec104.manager.Iec104ProtocolManager;
import com.runjian.rundo.iot.agent.iec104.module.Iec104Device;
import com.runjian.rundo.iot.agent.iec104.module.Iec104Protocol;
import com.runjian.rundo.iot.agent.iec104.module.Iec104ProtocolSeries;
import com.runjian.rundo.iot.agent.iec104.parser.APDUParser;
import com.runjian.rundo.iot.agent.iec104.parser.ASDUParser;
import com.runjian.rundo.iot.agent.iec104.parser.frame.*;
import com.runjian.rundo.iot.agent.iec104.session.Iec104TcpClientDeviceSession;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import io.netty.buffer.ByteBuf;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * Ice104协议解析
 *
 * @author ChenJinJie
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class Iec104DeviceMessageCodec implements DeviceMessageCodec {

    private final APDUParser apduParser;

    private final ActionHandleManager actionHandleManager;

    private final Iec104DeviceManager deviceManager;

    private final Iec104ProtocolManager protocolManager;

    private final ASDUParser asduParser;

    private final DeviceControlCache deviceControlCache;

    private final IIec104FrameStrategy IIec104FrameStrategy;

    @Override
    public List<DeviceMessage> decode(DeviceMessageDecodeContext messageContext) throws Exception {
        Iec104TcpClientDeviceSession iec104TcpClientDeviceSession = (Iec104TcpClientDeviceSession) messageContext.getDeviceSession();
        // 接收的信息
        byte[] payload = messageContext.getEncodedMessage().payloadAsBytes();
        // 处理解析
        AIec104Frame aIec104Frame = apduParser.analysis(payload);
        if (null == aIec104Frame) {
            if (log.isDebugEnabled()) {
                log.debug("未能转换侦数据:{}", payload);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("接收报文:{}", aIec104Frame.console());
        }
        // 处理I侦或S侦的接收序号
        iec104TcpClientDeviceSession.handleAIec104Frame(aIec104Frame);
        // 如果是控制侦
        List<DeviceMessage> deviceMessages = null;
        if (aIec104Frame instanceof IIec104Frame && ((IIec104Frame) aIec104Frame).getAsdu() != null && ((IIec104Frame) aIec104Frame).getAsdu() instanceof ControlFrame) {
            // 控制数据
            buildControlDeviceMessage(iec104TcpClientDeviceSession, aIec104Frame);
        } else {
            // 测量数据
            deviceMessages = buildDeviceMessage(iec104TcpClientDeviceSession, aIec104Frame);
        }
        // 回复报文。复用对方发过来的报文，只改变某些值，就在这里做。
        reply(aIec104Frame, iec104TcpClientDeviceSession);
        // 执行动作
        DeviceMessage deviceMessage = null;
        if (CollUtil.isNotEmpty(deviceMessages)) {
            // todo 正常来说一个 I 帧只会有一个设备的数据。东方电子现在这样设计可能会让一个 I 帧荣有多个设备的数据，先简单处理。
            deviceMessage = deviceMessages.get(0);
        }
        handleAction(iec104TcpClientDeviceSession, aIec104Frame, deviceMessage);
        // 判断是否有DeviceMessage
        return deviceMessages;
    }

    private void handleAction(Iec104TcpClientDeviceSession iec104TcpClientDeviceSession, AIec104Frame aIec104Frame, DeviceMessage deviceMessage) {
        if (ObjectUtil.isNotEmpty(aIec104Frame.action())) {
            actionHandleManager.handleAction(iec104TcpClientDeviceSession, deviceMessage, aIec104Frame.action());
        } else {
//            //无动作就发送S侦
//            actionHandleManager.handleAction(iec104TcpClientDeviceSession,deviceMessage,Action.SEND_S_DETECT);
        }
    }

    private void reply(AIec104Frame aIec104Frame, Iec104TcpClientDeviceSession iec104TcpClientDeviceSession) {
        // 获取回复侦
        AIec104Frame replyFrame = aIec104Frame.replyFrame();
        if (null != replyFrame) {
            if (log.isDebugEnabled()) {
                log.debug("回复报文:{}", aIec104Frame.console());
            }
            ByteBuf byteBuf = replyFrame.assemblyByteBuf();
            if (null != byteBuf) {
                iec104TcpClientDeviceSession.writeAndFlush(replyFrame.getFrameType(), byteBuf);
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("当前报文不需要回复");
            }
        }
    }

    /**
     * 转换数据上报
     *
     * @param iec104TcpClientDeviceSession 会话
     * @param aIec104Frame                 框架
     * @return 设备消息信息
     */
    private void buildControlDeviceMessage(Iec104TcpClientDeviceSession iec104TcpClientDeviceSession, AIec104Frame aIec104Frame) {
        if (!(aIec104Frame instanceof IIec104Frame)) {
            return;
        }
        IIec104Frame iIec104Frame = ((IIec104Frame) aIec104Frame);
        if (null == iIec104Frame.getAsdu()) {
            return;
        }
        String gatewayId = iec104TcpClientDeviceSession.getTcpNetworkClient().getId();
        ASDUFrame asduFrame = iIec104Frame.getAsdu();
        if (!(asduFrame instanceof BaseControlFrame)) {
            //不是控制侦
            return;
        }
        BaseControlFrame controlFrame = (BaseControlFrame) asduFrame;
        // 这个 externalId 不对，因为东方电子没有用这个字段
        String externalId = String.valueOf(controlFrame.getAddress());
        // 获取设备信息
        List<Iec104Device> devices = getDeviceByExternalId(gatewayId, iIec104Frame);
        for (Iec104Device device : devices) {
            // 这里使用配置中的外部ID，这个可以跟东方电子使用的点号对应上
            externalId = device.getExternalId();
            if (ObjectUtil.isNull(device)) {
                if (log.isDebugEnabled()) {
                    log.debug("未找到设备信息,网关Id：{},设备地址：{}", gatewayId, externalId);
                }
                return;
            }
            if (ObjectUtil.isNull(device)) {
                if (log.isDebugEnabled()) {
                    log.debug("未找到设备信息,网关Id：{},设备地址：{}", gatewayId, externalId);
                }
                return;
            }
            Map<String, Object> map = getSeriesMap(device, gatewayId, externalId, controlFrame, Constans.SERIES_DIRECTION_DOWN);
            if (ObjectUtil.isEmpty(map)) {
                log.warn("控制数据转换失败,数据地址未关联到相关的测量字典,网关Id：{},设备地址：{},协议Id:{}", gatewayId, externalId, device.getProtocolId());
                return;
            }
            boolean checkCommand = Boolean.FALSE;
            // 获取当前控制指标项,判断数据值是否一致
            CommonDeviceCommand deviceCommand = deviceControlCache.getCurrentDownCommon(device.getDeviceId());
            // 检查控制值与现有的控制指令是否相同
            if (deviceCommand != null) {
                // 检测当前等待反馈的控制项是否与设备反馈的一致
                checkCommand = this.checkMapEquality(map, deviceCommand.getCommands());
            }
            // 如果是,则进行对应的操作
            if (checkCommand) {
                // 一下步行动项
                controlFrame.next(checkCommand);
            }
            // 转换控制数据
            if (ObjectUtil.isNotNull(deviceCommand)) {
                // 发送控制事件
                if (controlFrame.getControlResult() != ControlResult.HANDLE) {
                    for (Map.Entry<String, Object> objectEntry : map.entrySet()) {
                        log.info("控制完成,控制指标项:{},控制结果:{}", objectEntry.getKey(), controlFrame.getControlResult());
                        if (controlFrame.getControlResult() == ControlResult.SUCCESS) {
                            DeviceEventPublisher.publishDeviceEvent(
                                    DeviceControlResponseEvent.success(device.getDeviceId(), deviceCommand, objectEntry.getKey()));
                        } else {
                            DeviceEventPublisher.publishDeviceEvent(DeviceControlResponseEvent.failed(device.getDeviceId(), deviceCommand,
                                    objectEntry.getKey(), controlFrame.getControlResult().getMessage()));
                        }
                    }
                } else {
                    log.info("控制正在执行中，流程还在执行中,等待执行，设备Id:{},控制数据:{}", device.getDeviceId(), map);
                }
            } else {
                log.warn("当前控制侦异常,待控制指令已无相关控制项,设备:{}，控制数据:{}", device.getDeviceId(), map);
            }
        }
    }

    /**
     * 转换数据上报
     *
     * @param iec104TcpClientDeviceSession 会话
     * @param aIec104Frame                 框架
     * @return 设备消息信息
     */
    private List<DeviceMessage> buildDeviceMessage(Iec104TcpClientDeviceSession iec104TcpClientDeviceSession, AIec104Frame aIec104Frame) {
        if (!(aIec104Frame instanceof IIec104Frame)) {
            return null;
        }
        IIec104Frame iIec104Frame = (IIec104Frame) aIec104Frame;
        if (null == iIec104Frame.getAsdu()) {
            return null;
        }
        String gatewayId = iec104TcpClientDeviceSession.getTcpNetworkClient().getId();
        ASDUFrame asduFrame = iIec104Frame.getAsdu();
        // 公共地址。 在标准 104 协议中这里存储着设备 ID ，但是东方电子没有使用这个字段。
        String externalId = String.valueOf(asduFrame.getAddress());
        // 获取设备信息
        List<Iec104Device> devices = getDeviceByExternalId(gatewayId, iIec104Frame);
        if (CollectionUtil.isEmpty(devices)) {
            if (log.isDebugEnabled()) {
                log.debug("未找到设备信息,网关Id：{},设备地址：{}", gatewayId, externalId);
            }
            return null;
        }
        List<DeviceMessage> decodedMessages = new ArrayList<>();
        for (Iec104Device device : devices) {
            externalId = device.getExternalId();
            DeviceMessage deviceMessage;
            if (device.getDeviceType().equals(DeviceType.A.getCode())) {
                // 获取设备为网关
                deviceMessage = new DefaultDeviceMessage(gatewayId, gatewayId, DeviceMessageType.VPP_INSTRUCTION_MESSAGE);
            } else {
                // 获取设备为网关
                deviceMessage = new DefaultDeviceMessage(gatewayId, gatewayId);
            }
            Map<String, Object> map = getSeriesMap(device, gatewayId, externalId, asduFrame, Constans.SERIES_DIRECTION_UP);
            // 封装子设备数据信息
            ZonedDateTime zonedDateTime = getZonedDateTime(device);
            DecodedMessage decodedMessage = new DefaultDecodedMessage(device.getDeviceId(), device.getExternalId(), gatewayId, zonedDateTime, map);
            deviceMessage.addDecodedMessages(decodedMessage);
            decodedMessages.add(deviceMessage);
        }
        return decodedMessages;
//        if (ObjectUtil.isEmpty(map)) {
//            if (log.isDebugEnabled()) {
//                log.debug("数据测量失败,信息地址未关联到任量测量数据字典,网关Id：{},设备地址：{},协议Id:{}", gatewayId, externalId, device.getProtocolId());
//            }
//            return null;
//        }
//        if (ObjectUtil.isNotEmpty(map)) {
//        }
//        return null;
    }

    private ZonedDateTime getZonedDateTime(Iec104Device device) {
        ZonedDateTime zonedDateTime;

        Iec104Protocol protocol = protocolManager.getProtocol(device.getProtocolId());
        if (protocol == null) {
            return LocalDateTime.now().atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC"));
        }
        Iec104ProtocolSeries protocol104Properties = protocol.getSeriesAddress(Integer.parseInt(device.getExternalId()), Constans.SERIES_DIRECTION_UP);
        if (protocol104Properties == null) {
            return LocalDateTime.now().atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC"));
        }
        String name = protocol104Properties.getName();
        boolean is96Point = name.contains("_") && name.contains(":");

        if (device.getDeviceType().equals(DeviceType.A.getCode()) || Boolean.TRUE.equals(!is96Point)) {
            // 获取设备为网关
            zonedDateTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC"));
        } else {
            int splitIndex = name.lastIndexOf("_");
            String timeStr = name.substring(splitIndex + 1);
            // 获取设备为网关
            zonedDateTime = generateZonedDateTime(timeStr);
        }
        return zonedDateTime;
    }

    private ZonedDateTime generateZonedDateTime(String timeStr) {
        LocalTime time = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm"));
        return LocalDate.now().atTime(time).atZone(ZoneId.systemDefault());
    }

    /**
     * 通过外部id获取设备
     * 通过 externalId 可以锁定唯一一个设备
     *
     * @param gatewayId   网关id
     * @param iec104Frame iec104 帧
     * @return {@link List }<{@link Iec104Device }>
     */
    public List<Iec104Device> getDeviceByExternalId(String gatewayId, IIec104Frame iec104Frame) {
        ASDUFrame asduFrame = iec104Frame.getAsdu();
        List<Iec104Device> iec104Devices = new ArrayList<>();
        for (InfoElement infoElement : asduFrame.getInfoElements()) {
            String externalId = String.valueOf(infoElement.getAddress());
            // 获取设备信息
            List<AgentDeviceDTO> deviceDTOS = deviceManager.getDeviceByExternalId(gatewayId, externalId);
            if (CollUtil.isNotEmpty(deviceDTOS)) {
                List<Iec104Device> iec104DeviceDTOS = deviceDTOS.stream().filter(Objects::nonNull).map(t -> (Iec104Device) t).collect(Collectors.toList());
                iec104Devices.addAll(iec104DeviceDTOS);
            }
        }
        return iec104Devices;
    }

    private Map<String, Object> getSeriesMap(Iec104Device device, String gatewayId, String externalId, ASDUFrame asduFrame, String direction) {
        List<InfoElement> infoElements = asduFrame.getInfoElements();
        if (ObjectUtil.isEmpty(infoElements)) {
            return null;
        }
        Iec104Protocol protocol = protocolManager.getProtocol(device.getProtocolId());
        if (ObjectUtil.isNull(protocol)) {
            if (log.isDebugEnabled()) {
                log.debug("未找到设备协议信息,网关Id：{},设备地址：{},协议Id:{}", gatewayId, externalId, device.getProtocolId());
            }
            return null;
        }
        Map<String, Object> map = new HashMap<>();
        infoElements.forEach(infoElement -> {
            if (infoElement.getAddress() != Integer.parseInt(externalId)) {
                return;
            }
            Iec104ProtocolSeries protocol104Properties = protocol.getSeriesAddress(infoElement.getAddress(), direction);
            if (null == protocol104Properties) {
                return;
            }
            Object object = infoElement.getValue();
            if (ObjectUtil.isEmpty(object)) {
                return;
            }
            if (object instanceof PointRemoteControl) {
                map.put(protocol104Properties.getSeries(), ((PointRemoteControl) object).getStatus() & 0x7F);
            } else if (object instanceof PointRemoteControl) {
                // 点位控制数据
                map.put(protocol104Properties.getSeries(), ((PointRemoteControl) object).getStatus());
            } else if (object instanceof Number) {
                if (ObjectUtil.isNotEmpty(protocol104Properties.getFactor()) && protocol104Properties.getFactor().doubleValue() > 0) {
                    // 根据变比进行计算
                    BigDecimal orgValue = getRealTypeValue(object);
                    BigDecimal calculateValue =
                            orgValue.divide(protocol104Properties.getFactor(), protocol104Properties.getScale(), RoundingMode.HALF_UP);
                    map.put(protocol104Properties.getSeries(), calculateValue);
                } else {
                    map.put(protocol104Properties.getSeries(), object);
                }
            }
        });
        return map;
    }

    /**
     * 下发指令
     *
     * @param encodeContext 设备消息编码上下文对象
     */
    @Override
    public void encode(DeviceMessageEncodeContext encodeContext) {
        CommonDeviceCommand commonDeviceCommand = encodeContext.getDeviceCommand();
        Iec104TcpClientDeviceSession deviceSession = (Iec104TcpClientDeviceSession) encodeContext.getDeviceSession();

        String deviceId = commonDeviceCommand.getDeviceId();
        List<Iec104Device> deviceList = deviceManager.getDeviceListById(deviceId);
        Assert.notEmpty(deviceList, "当前设备信息未定义,设备Id:{}", deviceId);
        if (CollUtil.isEmpty(deviceList)) {
            return;
        }
        List<String> externalIdList = deviceList.stream().map(Iec104Device::getExternalId).collect(Collectors.toList());
        // 因为东方电子所有的ProtocolId都一样，所以简单处理
        Iec104Protocol protocol = protocolManager.getProtocol(deviceList.get(0).getProtocolId());
        Assert.notNull(protocol, "当前iec104协议未定义,设备Id:{},协议Id:{}", deviceId, deviceList.get(0).getProtocolId());
        Map<String/*Series*/, Iec104ProtocolSeries> iec104ProtocolSeriesMap = protocol.getSeries().stream().filter(t -> t.getDirection().equals(Constans.SERIES_DIRECTION_DOWN))
                .filter(e -> externalIdList.contains(String.valueOf(e.getAddress())))
                .collect(Collectors.toMap(Iec104ProtocolSeries::getSeries, Function.identity()));

        Map<Ti, IIec104Frame> iec104FrameHashMap = new HashMap<>();
        // 同一类型，一次性下发
        commonDeviceCommand.getCommands().forEach((key, value) -> {
            Iec104ProtocolSeries iec104ProtocolSeries = iec104ProtocolSeriesMap.get(key);
            if (iec104ProtocolSeries != null) {
                Assert.notNull(iec104ProtocolSeries, "当前iec104协议未定义对应的指标项,设备Id:{},协议Id:{},指标项:{}", deviceId, protocol.getId(), key);
                Ti ti = iec104ProtocolSeries.getTi();
                Assert.notNull(ti, "当前iec104协议未定义对应的指标项的类型,设备Id:{},协议Id:{},指标项:{}", deviceId, protocol.getId(), key);
                IIec104Frame aIec104Frame = iec104FrameHashMap.computeIfAbsent(ti, (a) -> {
//                    return asduParser.iIec104Frame(ti, device.getAddress());
                    return asduParser.iIec104Frame(ti, Short.parseShort("1"));
                });
                InfoElement<Object> infoElement = InfoElement.builder().address(iec104ProtocolSeries.getAddress()).value(value).build();
                aIec104Frame.getAsdu().addInfoElement(infoElement);
	            IIec104FrameStrategy.handle(aIec104Frame);
            }
        });
        // 下发所有的104协议数据
        iec104FrameHashMap.forEach((key, value) -> {
            if (value.getAsdu() instanceof BaseControlFrame) {
                ((BaseControlFrame) value.getAsdu()).setSe((short) 1);
            }

            // 下发协议I侦
            deviceSession.writeIAndFlush(value);
        });
    }

    @Override
    public void active(Session deviceSession) {
        Iec104TcpClientDeviceSession iec104DeviceSession = (Iec104TcpClientDeviceSession) deviceSession;
        // 开始任务
        this.actionHandleManager.handleAction(iec104DeviceSession, null, Action.START_TASK);
    }

    @Override
    public void inactive(Session deviceSession) {
        Iec104TcpClientDeviceSession iec104DeviceSession = (Iec104TcpClientDeviceSession) deviceSession;
        // 关闭所有任务
        this.actionHandleManager.handleAction(iec104DeviceSession, null, Action.STOP_TASK, Action.CANCEL_ALL_TASK);
    }

    /**
     * 根据字段的实际类型，获取实际的数值类型
     *
     * @param value 数值类型
     * @return 实际的数值类型
     */
    public BigDecimal getRealTypeValue(Object value) {
        if (value instanceof Float) {
            return BigDecimal.valueOf((Float) value);
        } else if (value instanceof Integer) {
            return new BigDecimal(((Integer) value));
        } else if (value instanceof Double) {
            return BigDecimal.valueOf((Double) value);
        } else if (value instanceof Short) {
            return new BigDecimal(((Short) value));
        } else if (value instanceof Long) {
            return new BigDecimal(((Long) value));
        } else {
            return new BigDecimal(String.valueOf(value));
        }
    }

    @Override
    public void idle(Session deviceSession, SessionIdleType sessionIdleType) {
        if (sessionIdleType != SessionIdleType.ALL) {
            return;
        }
        UIec104Frame uIec104Frame = new UIec104Frame();
        uIec104Frame.setFunction(UFrameControlType.TESTFR_C);
        Iec104TcpClientDeviceSession iec104DeviceSession = (Iec104TcpClientDeviceSession) deviceSession;
        iec104DeviceSession.writeUAndFlush(uIec104Frame);
//        actionHandleManager.handleAction(iec104DeviceSession,null,Action.SEND_S_DETECT);
    }

    /**
     * 检查 map1 是否与 map2 中的全量数据相等
     *
     * @param map1 第一个 Map
     * @param map2 第二个 Map
     * @return 如果相等返回 true，否则返回 false
     */
    public boolean checkMapEquality(Map<String, Object> map1, Map<String, Object> map2) {
        for (Map.Entry<String, Object> entry : map1.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof Float) {
                value = new BigDecimal((float) value);
            } else if (value instanceof Double) {
                value = new BigDecimal((double) value);
            }
            if (value instanceof BigDecimal) {
                value = ((BigDecimal) value).setScale(2, BigDecimal.ROUND_HALF_UP);
            }
            if (!map2.containsKey(key) || !ObjectUtil.equals(map2.get(key), value)) {
                return false;
            }
        }
        return true;
    }
}
